package com.googlecode.openrtp.grizzly.rtcp.filter;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.inject.Inject;
import org.glassfish.grizzly.Buffer;
import org.glassfish.grizzly.filterchain.BaseFilter;
import org.glassfish.grizzly.filterchain.Filter;
import org.glassfish.grizzly.filterchain.FilterChainContext;
import org.glassfish.grizzly.filterchain.NextAction;
import com.googlecode.openrtp.spi.annotation.Priority;
import com.googlecode.openrtp.spi.annotation.RtcpPayload;
import com.googlecode.openrtp.spi.rtcp.RtcpCompoundPacket;
import com.googlecode.openrtp.spi.rtcp.RtcpHeaderPacket;
import static com.googlecode.openrtp.spi.rtcp.RtcpHeaderPacket.PT.*;
import com.googlecode.openrtp.spi.rtcp.RtcpPacket;

/**
 *
 * @author jocke
 */
@Priority(intValue = 1)
public class RtcpFilter extends BaseFilter {

    private static final Logger LOG = Logger.getLogger(RtcpFilter.class.getName());
    private Map<Integer, Filter> filters;

    @Inject
    public RtcpFilter(@RtcpPayload Map<Integer, Filter> map) {
        this.filters = new ConcurrentHashMap<>(map);
    }

    @Override
    public NextAction handleRead(FilterChainContext ctx) throws IOException {

        Buffer sourceBuffer = ctx.getMessage();

        RtcpCompoundPacket compoundPacket = new RtcpCompoundPacket();

        while (sourceBuffer.hasRemaining()) {

            RtcpHeaderPacket pkg = createHeader(sourceBuffer);

            if (isInvalidHeaderPacket(pkg, sourceBuffer, compoundPacket.size())) {
                return ctx.getStopAction();
            }

            if (filters.containsKey(pkg.getPt())) {
                ctx.setMessage(sourceBuffer);
                compoundPacket.putPacket(pkg.getPt(), delegateToPayloadFilter(ctx, pkg, sourceBuffer));

            } else {
                //TODO we should move the position to the next package boundry and just log here 
                throw new UnsupportedOperationException("No rtcp filter for payload type " + pkg.getPt());
            }

        }

        ctx.setMessage(compoundPacket);

        sourceBuffer.tryDispose();

        return super.handleRead(ctx);
    }

    private RtcpHeaderPacket createHeader(Buffer sourceBuffer) {

        if (sourceBuffer.remaining() >= RtcpHeaderPacket.PACKET_LENGTH) {
            int header = sourceBuffer.getInt();
            int v = (header >>> 30) & 0b11;
            int p = (header >>> 29) & 0b1;
            int rc = (header >>> 24) & 0b11111;
            int pt = (header >>> 16) & 0b11111111;
            int length = (header & 0b1111111111111111);
            return RtcpHeaderPacket.create(v, p, rc, pt, length);

        } else {
            return null;
        }
    }

    public boolean isInvalidHeaderPacket(RtcpHeaderPacket packet, Buffer buffer, int order) {

        if (packet == null) {
            LOG.log(Level.WARNING, "Failed to get RTCP header packet will be dropped");
            return true;

        } else if (packet.getV() != RtcpHeaderPacket.FINAL_VERSION) {
            LOG.log(Level.WARNING, "packet arrived with RTCP version {0} it will be dropped", packet.getV());
            return true;

        } else if (order == 0 && (packet.getPt() != SENDER_REPORT && packet.getPt() != RECEIVER_REPORT)) {
            LOG.log(Level.WARNING, "RTCP packages should start with RR({0}) or SR({1}) not {2}, packet will be dropped",
                    new Object[]{RECEIVER_REPORT, SENDER_REPORT, packet.getPt()});
            return true;

        } else if (order == 0 && isLastPacket(packet, buffer)) {
            LOG.log(Level.WARNING, "A RTCP package has to be compound");
            return true;

        } else if (!isLastPacket(packet, buffer) && packet.getP() != 0) {
            LOG.log(Level.WARNING, "Padding is only allowed in the last packet of a compound RTCP package");
            return true;

        } else {
            return false;
        }
    }

    private RtcpPacket delegateToPayloadFilter(FilterChainContext ctx, RtcpHeaderPacket pkg, Buffer sourceBuffer) throws IOException {

        ctx.getAttributes().setAttribute(RtcpHeaderPacket.class.getName(), pkg);

        int expectedPos = sourceBuffer.position() + pkg.wordBoundry();

        filters.get(pkg.getPt()).handleRead(ctx);

        if (expectedPos != sourceBuffer.position()) {
            //TODO maybe we should try to reset the position to the expected one
            LOG.log(Level.SEVERE, "Filter did not consume it's content {0} ", filters.get(pkg.getPt()).getClass().getName());
        }

        Object message = ctx.getMessage();

        if (message instanceof RtcpPacket) {
            return RtcpPacket.class.cast(message);

        } else {
            throw new IOException("Failed to create rtcp package");
        }
    }

    private boolean isLastPacket(RtcpHeaderPacket packet, Buffer buffer) {
        return packet.wordBoundry() == buffer.remaining();
    }

    @Override
    public NextAction handleWrite(FilterChainContext ctx) throws IOException {

        LOG.log(Level.INFO, "##################### Will send RTCP packet");

        return super.handleWrite(ctx);
    }
}
